5 Commits

5 changed files with 350 additions and 180 deletions

View File

@ -6,9 +6,33 @@ My standard System: Linux Mint 22 Cinnamon
- os import in common_tools.py replaced by other methods - os import in common_tools.py replaced by other methods
- If Wire-Py already runs, prevent further start - If Wire-Py already runs, prevent further start
- for loops with lists replaced by List Comprehensions - for loops with lists replaced by List Comprehensions
- Tunnel in tk.canvas for modern look
- Replace Download Button with Lx Tools installer - Replace Download Button with Lx Tools installer
### Added
02-07-2025
- Complete code for faulty f" string configuration dur addicted and fixed
- updater replace Downloadbutton with Lx Tools installer
- remove logviewer icon
- add settings.png icon for update button
### Added
27-06-2025
- Header added for more modern desing
- Sizes adjust the frames and labels improve
- More modern desing for listbox, Address, Dns and Endpoint
- ui works now better with rename button
- add Image class for manage Images
### Added ### Added
23-06-2025 23-06-2025

BIN
lx-icons/16/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

BIN
lx-icons/16/wg_vpn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

419
wirepy.py
View File

@ -6,6 +6,8 @@ import logging
import getpass import getpass
import shutil import shutil
import sys import sys
import os
import subprocess
import tkinter as tk import tkinter as tk
import webbrowser import webbrowser
from functools import partial from functools import partial
@ -24,7 +26,7 @@ from shared_libs.common_tools import (
Tooltip, Tooltip,
) )
from shared_libs.wp_app_config import AppConfig, Msg from shared_libs.wp_app_config import AppConfig, Image, Msg
class Wirepy(tk.Tk): class Wirepy(tk.Tk):
@ -39,6 +41,7 @@ class Wirepy(tk.Tk):
self.withdraw() self.withdraw()
self.my_tool_tip = None self.my_tool_tip = None
self.x_width = AppConfig.UI_CONFIG["window_size"][0] self.x_width = AppConfig.UI_CONFIG["window_size"][0]
self.y_height = AppConfig.UI_CONFIG["window_size"][1] self.y_height = AppConfig.UI_CONFIG["window_size"][1]
@ -48,23 +51,29 @@ class Wirepy(tk.Tk):
AppConfig.UI_CONFIG["resizable_window"][0], AppConfig.UI_CONFIG["resizable_window"][0],
AppConfig.UI_CONFIG["resizable_window"][1], AppConfig.UI_CONFIG["resizable_window"][1],
) )
self.minsize(
AppConfig.UI_CONFIG["window_size"][0],
AppConfig.UI_CONFIG["window_size"][1],
)
self.title(AppConfig.UI_CONFIG["window_title"]) self.title(AppConfig.UI_CONFIG["window_title"])
self.image_manager = Image()
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) ConfigManager.init(AppConfig.SETTINGS_FILE)
theme = ConfigManager.get("theme") theme = ConfigManager.get("theme")
ThemeManager.change_theme(self, theme) ThemeManager.change_theme(self, theme)
# Load the image file from the disk # Try to set icon
self.wg_icon = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_vpn"]) try:
icon = self.image_manager.load_image("icon_vpn")
# Set it as the window icon if icon:
self.iconphoto(True, self.wg_icon) self.iconphoto(True, icon)
except:
pass
# Add the widgets # Add the widgets
FrameWidgets(self).grid() FrameWidgets(self).grid()
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Center the window on the primary monitor # Center the window on the primary monitor
LxTools.center_window_cross_platform(self, self.x_width, self.y_height) LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
@ -87,13 +96,14 @@ class FrameWidgets(ttk.Frame):
self.dns = None self.dns = None
self.address = None self.address = None
self.auto_con = None self.auto_con = None
self.style = ttk.Style() self.image_manager = Image()
self.wg_vpn_start = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_start"]) self.columnconfigure(0, weight=1)
self.wg_vpn_stop = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_stop"]) self.rowconfigure(0, weight=1)
self.imp_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_import"]) self.columnconfigure(1, weight=1)
self.tr_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_trash"]) self.rowconfigure(1, weight=1)
self.exp_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_export"]) self.columnconfigure(2, weight=18)
self.warning_pic = tk.PhotoImage(file=AppConfig.IMAGE_PATHS["icon_error"]) self.rowconfigure(2, weight=1)
self.rowconfigure(3, weight=1)
# StringVar-Variables initialization # StringVar-Variables initialization
self.tooltip_state = tk.BooleanVar() self.tooltip_state = tk.BooleanVar()
@ -122,20 +132,10 @@ class FrameWidgets(ttk.Frame):
# Frame for Menu # Frame for Menu
self.menu_frame = ttk.Frame(self) self.menu_frame = ttk.Frame(self)
self.menu_frame.configure(relief="flat") self.menu_frame.grid(column=0, columnspan=3, row=1, sticky="we")
self.menu_frame.grid(column=0, row=0, columnspan=4, sticky="w")
# App Menu
self.version_lb = ttk.Label(self.menu_frame, text=AppConfig.VERSION)
self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff")
self.version_lb.grid(column=0, row=0, rowspan=4, padx=10)
Tooltip(
self.version_lb, f"Version: {AppConfig.VERSION[2:]}", self.tooltip_state
)
self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options")) self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options"))
self.options_btn.grid(column=1, columnspan=1, row=0) self.options_btn.grid(column=0, row=0)
Tooltip(self.options_btn, Msg.TTIP["settings"], self.tooltip_state) Tooltip(self.options_btn, Msg.TTIP["settings"], self.tooltip_state)
@ -147,9 +147,8 @@ class FrameWidgets(ttk.Frame):
command=lambda: self.update_setting(self.set_update.get()), command=lambda: self.update_setting(self.set_update.get()),
variable=self.set_update, variable=self.set_update,
) )
self.updates_lb = ttk.Label(self.menu_frame, textvariable=self.update_label) 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(column=2, row=0)
self.updates_lb.grid_remove() self.updates_lb.grid_remove()
self.update_label.trace_add("write", self.update_label_display) self.update_label.trace_add("write", self.update_label_display)
self.update_foreground.trace_add("write", self.update_label_display) self.update_foreground.trace_add("write", self.update_label_display)
@ -177,69 +176,148 @@ class FrameWidgets(ttk.Frame):
self.about_btn = ttk.Button( 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.about_btn.grid(column=1, row=0)
self.readme = tk.Menu(self)
self.a = Tunnel.get_active() self.a = Tunnel.get_active()
# Label Frame 1 # Header Frame
self.lb_frame_btn_lbox = ttk.Frame(self) # Festlegen der Farbe
self.lb_frame_btn_lbox.configure(relief="flat") self.header_frame = tk.Frame(self, bg="#2c3e50")
self.lb_frame_btn_lbox.grid(column=0, rowspan=3, row=1) self.wg_icon_header_frame = tk.Frame(self.header_frame, bg="#2c3e50")
self.header_label = tk.Label(
# Label Frame 2 self.header_frame,
self.lb_frame = ttk.Frame(self) text=_("Lx Tools Wire-Py"),
self.lb_frame.configure(relief="solid") font=("Helvetica", 12, "bold"),
self.lb_frame.grid(column=2, row=2, sticky="snew", padx=20, pady=5) fg="#ffffff",
bg="#2c3e50",
# Label Frame 3
self.lb_frame2 = ttk.Frame(self)
self.lb_frame2.configure(relief="solid")
self.lb_frame2.grid(column=2, row=3, sticky="snew", padx=20, pady=5)
# Bottom Frame 4
self.lb_frame3 = ttk.Frame(self)
self.lb_frame3.configure(relief="flat")
self.lb_frame3.grid(
column=0, row=5, columnspan=4, sticky="snew", padx=2, pady=2
) )
# Bottom Frame 5 self.version_label = tk.Label(
self.lb_frame4 = ttk.Frame(self) self.header_frame,
self.lb_frame4.configure(relief="flat") text=f"{AppConfig.VERSION}{Msg.STR['header_left_bottom']}",
self.lb_frame4.grid(column=2, row=5, columnspan=3, sticky="e", padx=15) font=("Helvetica", 9),
fg="#bdc3c7",
bg="#2c3e50",
)
self.info_label = tk.Label(
self.header_frame,
text=Msg.STR["header_right_top"],
font=("Helvetica", 10),
fg="#ecf0f1",
bg="#2c3e50",
)
self.header_frame.grid(column=0, columnspan=3, row=0, sticky="nsew")
self.wg_icon_header_frame.grid(column=0, row=0, rowspan=2, sticky="w")
self.wg_icon_header_label = tk.Label(
self.wg_icon_header_frame,
image=self.image_manager.load_image("icon_header"),
bg="#2c3e50",
)
self.wg_icon_header_label.grid(column=0, row=0, sticky="e", ipadx=10)
self.header_label.grid(
column=1,
row=0,
sticky="w",
padx=(5, 20),
pady=(10, 0),
ipady=4,
)
self.version_label.grid(column=1, row=1, sticky="w", padx=(5, 20), pady=(0, 10))
self.info_label.grid(column=2, row=0, sticky="ne", padx=(10, 10), pady=(10, 0))
self.header_frame.columnconfigure(1, weight=1, pad=2)
self.header_frame.rowconfigure(0, weight=1)
# Frame for Control Buttons (Start, Stop, Import, Trash, Export)
self.control_buttons_frame = ttk.Frame(self)
self.control_buttons_frame.grid(column=0, row=2, sticky="w", padx=(15, 0))
self.control_buttons_frame.columnconfigure(0, weight=1)
self.control_buttons_frame.rowconfigure(2, weight=1)
# Frame for Listbox and Scrollbar
self.list_container_frame = ttk.Frame(self)
self.list_container_frame.grid(column=1, row=2, sticky="nsew", pady=3)
self.list_container_frame.columnconfigure(1, weight=1)
self.list_container_frame.rowconfigure(2, weight=1)
self.list_frame = ttk.LabelFrame(self.list_container_frame, text=_("Tunnels"))
self.list_frame.grid(column=0, row=0, sticky="nsew", padx=10, ipady=25)
# Listbox with Scrollbar
self.list_box = tk.Listbox(self.list_frame, selectmode="single")
self.list_box.config(
relief="flat",
font=("Ubuntu", 12, "bold"),
)
self.list_box.grid(column=0, row=0, sticky="nsew")
self.list_box.event_add("<<ClickEvent>>", "<Button-1>")
self.list_box.bind("<<ClickEvent>>", self.enable_check_box)
self.scrollbar = ttk.Scrollbar(
self.list_frame, orient="vertical", command=self.list_box.yview
)
self.scrollbar.grid(column=1, row=0, sticky="ns")
self.list_box.configure(yscrollcommand=self.scrollbar.set)
self.scrollbar.columnconfigure(1, weight=1)
self.scrollbar.rowconfigure(0, weight=1)
# Frame for Active Tunnel, Interface and Peer
# Right Side Frame
self.right_side_frame = ttk.Frame(self)
self.right_side_frame.grid(column=2, row=2, sticky="nsew")
self.right_side_frame.columnconfigure(2, weight=1)
self.right_side_frame.rowconfigure(2, weight=1)
# Show active Label # Show active Label
self.select_tunnel = None self.select_tunnel = None
self.lb = ttk.Label(self, text=_("Active: ")) self.active_frame = ttk.LabelFrame(
self.lb.config(font=("Ubuntu", 11, "bold")) self.right_side_frame, text=_("Active Tunnel")
self.lb.grid(column=2, row=1, padx=15, pady=4, sticky="w") )
self.active_frame.grid(
column=0, row=0, sticky="nsew", padx=10, pady=5, columnspan=3
)
self.active_frame.columnconfigure(0, weight=1)
self.active_frame.rowconfigure(0, weight=1)
# Interface Label Frame
self.interface_frame = ttk.LabelFrame(
self.right_side_frame, text=_("Interface")
)
self.interface_frame.grid(
column=0, row=1, sticky="nsew", padx=10, pady=5, columnspan=3
)
self.interface_frame.columnconfigure(0, weight=1)
self.interface_frame.rowconfigure(1, weight=1)
# Peer Label Frame
self.peer_frame = ttk.LabelFrame(self.right_side_frame, text=_("Peer"))
self.peer_frame.grid(
column=0, row=2, sticky="nsew", padx=10, pady=5, columnspan=3
)
self.peer_frame.columnconfigure(0, weight=1)
self.peer_frame.rowconfigure(2, weight=1)
# Auto Start Label Frame
self.autoconnect_frame = ttk.Frame(self)
self.autoconnect_frame.grid(column=0, row=3, columnspan=2, sticky="w")
# Rename Frame
self.rename_frame = ttk.Frame(self)
self.rename_frame.grid(column=2, padx=10, row=3, sticky="nsew")
self.rename_frame.columnconfigure(0, weight=2)
self.rename_frame.columnconfigure(1, weight=1)
# Label to Show active Tunnel # Label to Show active Tunnel
self.str_var = tk.StringVar(value=self.a) self.str_var = tk.StringVar(value=self.a)
self.color_label() self.color_label()
# Interface Label # Interface Label
self.interface = ttk.Label(self.lb_frame, text=_("Interface")) self.interface = ttk.Label(self.interface_frame)
self.interface.grid(column=0, row=3, sticky="we", padx=120) self.interface.grid(column=0, row=4, sticky="we")
self.interface.config(font=("Ubuntu", 9)) self.interface.config(font=("Ubuntu", 9))
# Peer Label # Peer Label
self.peer = ttk.Label(self.lb_frame2, text=_("Peer")) self.peer = ttk.Label(self.peer_frame)
self.peer.config(font=("Ubuntu", 9)) self.peer.config(font=("Ubuntu", 9))
self.peer.grid(column=0, row=4, sticky="we", padx=130) self.peer.grid(column=0, row=5, sticky="we")
# Listbox with Scrollbar
self.l_box = tk.Listbox(self.lb_frame_btn_lbox, selectmode="single")
self.l_box.config(relief="ridge", font=("Ubuntu", 12, "bold"))
self.l_box.grid(column=1, rowspan=4, row=0, sticky="ns")
self.l_box.event_add("<<ClickEvent>>", "<Button-1>")
self.l_box.bind("<<ClickEvent>>", self.enable_check_box)
self.scrollbar = ttk.Scrollbar(
self.lb_frame_btn_lbox, orient="vertical", command=self.l_box.yview
)
self.scrollbar.grid(column=1, rowspan=4, row=0, sticky="nse")
self.l_box.configure(yscrollcommand=self.scrollbar.set)
# Tunnel List # Tunnel List
self.tl = Tunnel.parse_files_to_dictionary(directory=AppConfig.TEMP_DIR) self.tl = Tunnel.parse_files_to_dictionary(directory=AppConfig.TEMP_DIR)
@ -247,8 +325,8 @@ class FrameWidgets(ttk.Frame):
AppConfig.ensure_directories() AppConfig.ensure_directories()
for tunnels, values in self.tl.items(): for tunnels, values in self.tl.items():
self.l_box.insert("end", tunnels) self.list_box.insert("end", tunnels)
self.l_box.update() self.list_box.update()
# Button Vpn # Button Vpn
if self.a != "": if self.a != "":
@ -267,52 +345,55 @@ class FrameWidgets(ttk.Frame):
# Button Import # Button Import
self.btn_i = ttk.Button( self.btn_i = ttk.Button(
self.lb_frame_btn_lbox, self.control_buttons_frame,
image=self.imp_pic, image=self.image_manager.load_image("icon_import"),
command=self.import_sl, command=self.import_sl,
padding=0, padding=0,
) )
self.btn_i.grid(column=0, row=1, padx=15, pady=8) self.btn_i.grid(column=0, row=1, pady=8)
Tooltip(self.btn_i, Msg.TTIP["import_tl"], self.tooltip_state) Tooltip(self.btn_i, Msg.TTIP["import_tl"], self.tooltip_state)
# Button Trash # Button Trash
self.btn_tr = ttk.Button( self.btn_tr = ttk.Button(
self.lb_frame_btn_lbox, self.control_buttons_frame,
image=self.tr_pic, image=self.image_manager.load_image("icon_trash"),
command=self.delete, command=self.delete,
padding=0, padding=0,
style="CButton.TButton",
) )
self.btn_tr.grid(column=0, row=2, padx=15, pady=8) self.btn_tr.grid(column=0, row=2, pady=8)
if self.l_box.size() == 0: if self.list_box.size() == 0:
Tooltip(self.btn_tr, Msg.TTIP["trash_tl_info"], self.tooltip_state) Tooltip(self.btn_tr, Msg.TTIP["trash_tl_info"], self.tooltip_state)
else: else:
Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state) Tooltip(self.btn_tr, Msg.TTIP["trash_tl"], self.tooltip_state)
# Button Export # Button Export
self.btn_exp = ttk.Button( self.btn_exp = ttk.Button(
self.lb_frame_btn_lbox, self.control_buttons_frame,
image=self.exp_pic, image=self.image_manager.load_image("icon_export"),
command=lambda: Tunnel.export(), command=lambda: Tunnel.export(),
padding=0, padding=0,
) )
self.btn_exp.grid(column=0, row=3, padx=15, pady=8) self.btn_exp.grid(column=0, row=3, pady=8)
self.btn_exp.columnconfigure(0, weight=1)
self.btn_exp.rowconfigure(3, weight=1)
if self.l_box.size() == 0: if self.list_box.size() == 0:
Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state) Tooltip(self.btn_exp, Msg.TTIP["export_tl_info"], self.tooltip_state)
else: else:
Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state) Tooltip(self.btn_exp, Msg.TTIP["export_tl"], self.tooltip_state)
# Label Entry # Label Entry
self.lb_rename = ttk.Entry(self.lb_frame4, width=20) self.lb_rename = ttk.Entry(self.rename_frame)
self.lb_rename.grid(column=2, row=0, padx=8, pady=10, sticky="ne") self.lb_rename.grid(column=0, row=0, padx=8, pady=10, sticky="ne")
self.lb_rename.config(width=15)
self.lb_rename.insert(0, _("Max. 12 characters!")) self.lb_rename.insert(0, _("Max. 12 characters!"))
self.lb_rename.config(state="disable") self.lb_rename.config(state="disable")
if self.l_box.size() != 0: if self.list_box.size() != 0:
Tooltip( Tooltip(
self.lb_rename, self.lb_rename,
Msg.TTIP["rename_tl"], Msg.TTIP["rename_tl"],
@ -331,14 +412,13 @@ class FrameWidgets(ttk.Frame):
# Button Rename # Button Rename
self.btn_rename = ttk.Button( self.btn_rename = ttk.Button(
self.lb_frame4, self.rename_frame,
text=_("Rename"), text=_("Rename"),
state="disable", state="disable",
command=self.tl_rename, command=self.tl_rename,
padding=4, width=15,
style="RnButton.TButton",
) )
self.btn_rename.grid(column=3, row=0, padx=5, pady=10, sticky="ne") self.btn_rename.grid(column=1, row=0, pady=10, sticky="nsew")
# Check Buttons # Check Buttons
self.selected_option = tk.IntVar() self.selected_option = tk.IntVar()
@ -347,19 +427,21 @@ class FrameWidgets(ttk.Frame):
# Frame for Labels, Entry and Button # Frame for Labels, Entry and Button
self.autoconnect = ttk.Label( self.autoconnect = ttk.Label(
self.lb_frame3, textvariable=self.autoconnect_var, width=15 self.autoconnect_frame, textvariable=self.autoconnect_var
) )
self.autoconnect.config(font=("Ubuntu", 11)) self.autoconnect.config(font=("Ubuntu", 11))
self.autoconnect.grid(column=1, row=0, sticky="e", pady=19) self.autoconnect.grid(column=1, row=0, pady=10, sticky="nsew")
self.autoconnect.columnconfigure(1, weight=1)
self.autoconnect.rowconfigure(0, weight=1)
self.wg_autostart = ttk.Checkbutton( self.wg_autostart = ttk.Checkbutton(
self.lb_frame3, self.autoconnect_frame,
text=_("Autoconnect on:"), text=_("Autoconnect on:"),
variable=self.selected_option, variable=self.selected_option,
command=self.box_set, command=self.box_set,
) )
self.wg_autostart.grid(column=0, row=0, pady=15, padx=15, sticky="nw") self.wg_autostart.grid(column=0, row=0, pady=10, padx=(10, 0), sticky="ew")
if self.l_box.size() >= 1 and len(self.l_box.curselection()) >= 1: if self.list_box.size() >= 1 and len(self.list_box.curselection()) >= 1:
Tooltip( Tooltip(
self.wg_autostart, self.wg_autostart,
Msg.TTIP["autostart"], Msg.TTIP["autostart"],
@ -368,7 +450,7 @@ class FrameWidgets(ttk.Frame):
y_offset=-40, y_offset=-40,
) )
if self.l_box.size() == 0: if self.list_box.size() == 0:
Tooltip( Tooltip(
self.wg_autostart, self.wg_autostart,
Msg.TTIP["autostart_info"], Msg.TTIP["autostart_info"],
@ -401,6 +483,15 @@ class FrameWidgets(ttk.Frame):
else: else:
self.updates_lb.grid_remove() self.updates_lb.grid_remove()
def updater(self):
"""Start the lxtools_installer"""
tmp_dir = Path("/tmp/lxtools")
Path.mkdir(tmp_dir, exist_ok=True)
os.chdir(tmp_dir)
result = subprocess.run(["/usr/local/bin/lxtools_installer"], check=False)
if result.returncode != 0:
MessageDialog("error", result.stderr)
# Update the labels based on the result # Update the labels based on the result
def update_ui_for_update(self, res): def update_ui_for_update(self, res):
"""Update UI elements based on an update check result""" """Update UI elements based on an update check result"""
@ -437,25 +528,20 @@ class FrameWidgets(ttk.Frame):
else: else:
self.set_update.set(value=0) self.set_update.set(value=0)
update_text = f"Update {res} {_('available!')}"
# Clear the label text since we'll show the button instead # Clear the label text since we'll show the button instead
self.update_label.set("") self.update_label.set("")
# Create the update button # Create the update button
self.update_btn = ttk.Menubutton(self.menu_frame, text=update_text) self.update_btn = ttk.Button(
self.update_btn.grid(column=4, columnspan=3, row=0, padx=0) self.menu_frame,
Tooltip( image=self.image_manager.load_image("update"),
self.update_btn, _("Click to download new version"), self.tooltip_state style="Toolbutton",
command=self.updater,
) )
self.update_btn.grid(column=5, row=0, padx=0)
self.download = tk.Menu(self, relief="flat") Tooltip(
self.update_btn.configure(menu=self.download, style="Toolbutton") self.update_btn, _("Click to install new version"), self.tooltip_state
self.download.add_command(
label=_("Download"),
command=lambda: GiteaUpdate.download(
f"{AppConfig.DOWNLOAD_URL}/{res}.zip", res
),
) )
@staticmethod @staticmethod
@ -479,7 +565,7 @@ class FrameWidgets(ttk.Frame):
None, None,
partial(webbrowser.open, "https://git.ilunix.de/punix/Wire-Py"), partial(webbrowser.open, "https://git.ilunix.de/punix/Wire-Py"),
], ],
icon=AppConfig.IMAGE_PATHS["icon_vpn"], icon="/usr/share/icons/lx-icons/64/wg_vpn.png",
wraplength=420, wraplength=420,
) )
@ -562,7 +648,9 @@ class FrameWidgets(ttk.Frame):
new_theme = "dark" if current_theme == "light" else "light" new_theme = "dark" if current_theme == "light" else "light"
ThemeManager.change_theme(self, new_theme, new_theme) ThemeManager.change_theme(self, new_theme, new_theme)
self.color_label() self.color_label()
self.header_label.config(fg="#ffffff")
self.update_theme_label() # Update the theme label self.update_theme_label() # Update the theme label
# Update Menulfield # Update Menulfield
self.settings.entryconfigure(2, label=self.theme_label.get()) self.settings.entryconfigure(2, label=self.theme_label.get())
@ -571,14 +659,16 @@ class FrameWidgets(ttk.Frame):
Start Button Start Button
""" """
self.btn_stst = ttk.Button( self.btn_stst = ttk.Button(
self.lb_frame_btn_lbox, self.control_buttons_frame,
image=self.wg_vpn_start, image=self.image_manager.load_image("icon_start"),
command=lambda: self.wg_switch("start"), command=lambda: self.wg_switch("start"),
padding=0, padding=0,
) )
self.btn_stst.grid(column=0, row=0, padx=5, pady=8) self.btn_stst.grid(column=0, row=0, pady=8)
self.btn_stst.columnconfigure(0, weight=1)
self.btn_stst.rowconfigure(0, weight=1)
if self.l_box.size() == 0: if self.list_box.size() == 0:
Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state) Tooltip(self.btn_stst, Msg.TTIP["empty_list"], self.tooltip_state)
else: else:
Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state) Tooltip(self.btn_stst, Msg.TTIP["start_tl"], self.tooltip_state)
@ -590,28 +680,32 @@ class FrameWidgets(ttk.Frame):
if ConfigManager.get("theme") == "light": if ConfigManager.get("theme") == "light":
self.lb_tunnel = ttk.Label( self.lb_tunnel = ttk.Label(
self, textvariable=self.str_var, foreground="green" self.active_frame, textvariable=self.str_var, foreground="green"
) )
else: else:
self.lb_tunnel = ttk.Label( self.lb_tunnel = ttk.Label(
self, textvariable=self.str_var, foreground="yellow" self.active_frame, textvariable=self.str_var, foreground="yellow"
) )
self.lb_tunnel.config(font=("Ubuntu", 11, "bold")) self.lb_tunnel.config(font=("Ubuntu", 11, "bold"))
self.lb_tunnel.grid(column=2, padx=10, row=1) self.lb_tunnel.grid(column=0, row=0, padx=10, pady=(0, 10), sticky="n")
self.lb_tunnel.columnconfigure(0, weight=1)
self.lb_tunnel.rowconfigure(0, weight=1)
def stop(self) -> None: def stop(self) -> None:
""" """
Stop Button Stop Button
""" """
self.btn_stst = ttk.Button( self.btn_stst = ttk.Button(
self.lb_frame_btn_lbox, self.control_buttons_frame,
image=self.wg_vpn_stop, image=self.image_manager.load_image("icon_stop"),
command=lambda: self.wg_switch("stop"), command=lambda: self.wg_switch("stop"),
padding=0, padding=0,
) )
self.btn_stst.grid(column=0, row=0, padx=5, pady=8) self.btn_stst.grid(column=0, row=0, pady=8)
self.btn_stst.columnconfigure(0, weight=1)
self.btn_stst.rowconfigure(0, weight=1)
Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state) Tooltip(self.btn_stst, Msg.TTIP["stop_tl"], self.tooltip_state)
@ -635,11 +729,11 @@ class FrameWidgets(ttk.Frame):
) )
data_import, key_name = Tunnel.parse_files_to_dictionary(filepath=filepath) data_import, key_name = Tunnel.parse_files_to_dictionary(filepath=filepath)
if CryptoUtil.find_key(f"{data_import[key_name]["PrivateKey"]}="): if CryptoUtil.find_key(f"{data_import[key_name]['PrivateKey']}="):
MessageDialog("error", Msg.STR["tl_exist"], title=Msg.STR["imp_err"]) MessageDialog("error", Msg.STR["tl_exist"], title=Msg.STR["imp_err"])
elif not CryptoUtil.is_valid_base64( elif not CryptoUtil.is_valid_base64(
f"{data_import[key_name]["PrivateKey"]}=" f"{data_import[key_name]['PrivateKey']}="
): # 2. Second check: Is it valid Base64? ): # 2. Second check: Is it valid Base64?
MessageDialog( MessageDialog(
"error", "error",
@ -699,11 +793,11 @@ class FrameWidgets(ttk.Frame):
AppConfig.ensure_directories() AppConfig.ensure_directories()
self.str_var.set("") self.str_var.set("")
self.a = Tunnel.get_active() self.a = Tunnel.get_active()
self.l_box.insert(0, self.a) self.list_box.insert(0, self.a)
self.wg_autostart.configure(state="normal") self.wg_autostart.configure(state="normal")
self.l_box.selection_clear(0, tk.END) self.list_box.selection_clear(0, tk.END)
self.l_box.update() self.list_box.update()
self.l_box.selection_set(0) self.list_box.selection_set(0)
Tooltip( Tooltip(
self.wg_autostart, self.wg_autostart,
@ -748,8 +842,8 @@ class FrameWidgets(ttk.Frame):
delete Wireguard Tunnel delete Wireguard Tunnel
""" """
try: try:
self.select_tunnel = self.l_box.curselection() self.select_tunnel = self.list_box.curselection()
select_tl = self.l_box.get(self.select_tunnel[0]) select_tl = self.list_box.get(self.select_tunnel[0])
process: CompletedProcess[str] = run( process: CompletedProcess[str] = run(
["nmcli", "connection", "delete", select_tl], ["nmcli", "connection", "delete", select_tl],
@ -763,7 +857,7 @@ class FrameWidgets(ttk.Frame):
f"{process.stderr} Code: {process.returncode}", exc_info=True f"{process.stderr} Code: {process.returncode}", exc_info=True
) )
self.l_box.delete(self.select_tunnel[0]) self.list_box.delete(self.select_tunnel[0])
Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat") Path.unlink(f"{AppConfig.CONFIG_DIR}/{select_tl}.dat")
if select_tl == ConfigManager.get("autostart"): if select_tl == ConfigManager.get("autostart"):
@ -774,7 +868,7 @@ class FrameWidgets(ttk.Frame):
self.wg_autostart.configure(state="disabled") self.wg_autostart.configure(state="disabled")
# for disabling checkbox when Listbox empty # for disabling checkbox when Listbox empty
if self.l_box.size() == 0: if self.list_box.size() == 0:
self.wg_autostart.configure(state="disabled") self.wg_autostart.configure(state="disabled")
self.lb_rename.configure(state="disabled") self.lb_rename.configure(state="disabled")
Tooltip( Tooltip(
@ -793,12 +887,12 @@ class FrameWidgets(ttk.Frame):
if self.a != "" and self.a == select_tl: if self.a != "" and self.a == select_tl:
self.str_var.set(value="") self.str_var.set(value="")
self.start() self.start()
self.l_box.update() self.list_box.update()
self.reset_fields() self.reset_fields()
except IndexError: except IndexError:
if self.l_box.size() != 0: if self.list_box.size() != 0:
MessageDialog("info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) MessageDialog("info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"])
@ -811,7 +905,7 @@ class FrameWidgets(ttk.Frame):
checkbox for enable autostart Tunnel checkbox for enable autostart Tunnel
""" """
AppConfig.get_autostart_content() AppConfig.get_autostart_content()
if self.l_box.size() != 0: if self.list_box.size() != 0:
self.wg_autostart.configure(state="normal") self.wg_autostart.configure(state="normal")
self.lb_rename.config(state="normal") self.lb_rename.config(state="normal")
self.lb_rename.delete(0, tk.END) self.lb_rename.delete(0, tk.END)
@ -838,13 +932,14 @@ class FrameWidgets(ttk.Frame):
self.autoconnect_var.set(self.auto_con) self.autoconnect_var.set(self.auto_con)
self.autoconnect = ttk.Label( self.autoconnect = ttk.Label(
self.lb_frame3, self.autoconnect_frame,
textvariable=self.autoconnect_var, textvariable=self.autoconnect_var,
foreground="#0071ff", foreground="#0071ff",
width=15, width=18,
) )
self.autoconnect.config(font=("Ubuntu", 11)) self.autoconnect.config(font=("Ubuntu", 11))
self.autoconnect.grid(column=1, row=0, sticky="e", pady=19) self.autoconnect.grid(column=1, row=0, sticky="ew", pady=19)
self.autoconnect.rowconfigure(0, weight=1)
def box_set(self) -> None: def box_set(self) -> None:
""" """
@ -858,13 +953,13 @@ class FrameWidgets(ttk.Frame):
to disable the autostart. to disable the autostart.
""" """
try: try:
select_tunnel = self.l_box.curselection() select_tunnel = self.list_box.curselection()
select_tl = self.l_box.get(select_tunnel[0]) select_tl = self.list_box.get(select_tunnel[0])
if self.selected_option.get() == 0: if self.selected_option.get() == 0:
ConfigManager.set("autostart", "off") ConfigManager.set("autostart", "off")
if self.l_box.size() == 0: if self.list_box.size() == 0:
self.wg_autostart.configure(state="disabled") self.wg_autostart.configure(state="disabled")
if self.selected_option.get() >= 1: if self.selected_option.get() >= 1:
@ -906,8 +1001,8 @@ class FrameWidgets(ttk.Frame):
else: else:
try: try:
self.select_tunnel = self.l_box.curselection() self.select_tunnel = self.list_box.curselection()
select_tl = self.l_box.get(self.select_tunnel[0]) select_tl = self.list_box.get(self.select_tunnel[0])
# nmcli connection modify old connection.id iphone # nmcli connection modify old connection.id iphone
process: CompletedProcess[str] = run( process: CompletedProcess[str] = run(
@ -935,9 +1030,9 @@ class FrameWidgets(ttk.Frame):
if select_tl == ConfigManager.get("autostart"): if select_tl == ConfigManager.get("autostart"):
ConfigManager.set("autostart", self.lb_rename.get()) ConfigManager.set("autostart", self.lb_rename.get())
self.autoconnect_var.set(value=self.lb_rename.get()) self.autoconnect_var.set(value=self.lb_rename.get())
self.l_box.delete(self.select_tunnel[0]) self.list_box.delete(self.select_tunnel[0])
self.l_box.insert("end", self.lb_rename.get()) self.list_box.insert("end", self.lb_rename.get())
self.l_box.update() self.list_box.update()
self.lb_rename.delete(0, tk.END) self.lb_rename.delete(0, tk.END)
self.update_connection_display() self.update_connection_display()
@ -968,34 +1063,36 @@ class FrameWidgets(ttk.Frame):
in the UI using ttk.Label widgets. in the UI using ttk.Label widgets.
Creates three labels for address, DNS, and endpoint with Creates three labels for address, DNS, and endpoint with
specific styling (color, font), positioning them in a specific styling (color, font), positioning them in a
grid layout (`lb_frame` and `lb_frame2`). grid layout (`lb_frame` and `peer_frame`).
Each label is linked to a corresponding text variable Each label is linked to a corresponding text variable
(`self.add`, `self.DNS`, `self.enp`) for dynamic data updates. (`self.add`, `self.DNS`, `self.enp`) for dynamic data updates.
""" """
# Address Label # Address Label
self.address = ttk.Label( self.address = ttk.Label(
self.lb_frame, textvariable=self.add, foreground="#0071ff" self.interface_frame, textvariable=self.add, foreground="#0071ff"
) )
self.address.grid(column=0, row=5, sticky="w", padx=10, pady=6) self.address.grid(column=0, row=5, sticky="w", padx=10, pady=(0, 20))
self.address.config(font=("Ubuntu", 9)) self.address.config(font=("Ubuntu", 9))
# DNS Label # DNS Label
self.dns = ttk.Label(self.lb_frame, textvariable=self.DNS, foreground="#0071ff") self.dns = ttk.Label(
self.dns.grid(column=0, row=7, sticky="w", padx=10, pady=6) self.interface_frame, textvariable=self.DNS, foreground="#0071ff"
)
self.dns.grid(column=0, row=7, sticky="w", padx=10, pady=(0, 20))
self.dns.config(font=("Ubuntu", 9)) self.dns.config(font=("Ubuntu", 9))
# Endpoint Label # Endpoint Label
self.endpoint = ttk.Label( self.endpoint = ttk.Label(
self.lb_frame2, textvariable=self.enp, foreground="#0071ff" self.peer_frame, textvariable=self.enp, foreground="#0071ff"
) )
self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=20) self.endpoint.grid(column=0, row=8, sticky="w", padx=10, pady=(0, 30))
self.endpoint.config(font=("Ubuntu", 9)) self.endpoint.config(font=("Ubuntu", 9))
def wg_switch(self, event=None) -> None: def wg_switch(self, event=None) -> None:
""" """
Manages switching between active and inactiveVPN connections. Manages switching between active and inactiveVPN connections.
If no tunnel is selected (`self.a == ""`), it starts a new connection If no tunnel is selected (`self.a == ""`), it starts a new connection
with the selected tunnel from the listbox (`l_box`). with the selected tunnel from the listbox (`list_box`).
Otherwise, it stops the current connection and updates Otherwise, it stops the current connection and updates
tunnel data using `handle_tunnel_data`. tunnel data using `handle_tunnel_data`.
Handles errors like `IndexError` by displaying appropriate Handles errors like `IndexError` by displaying appropriate
@ -1003,8 +1100,8 @@ class FrameWidgets(ttk.Frame):
""" """
try: try:
if self.a == "": if self.a == "":
self.select_tunnel = self.l_box.curselection() self.select_tunnel = self.list_box.curselection()
select_tl = self.l_box.get(self.select_tunnel[0]) select_tl = self.list_box.get(self.select_tunnel[0])
self.handle_connection_state("start", select_tl) self.handle_connection_state("start", select_tl)
else: else:
@ -1014,7 +1111,7 @@ class FrameWidgets(ttk.Frame):
except IndexError: except IndexError:
if self.l_box.size() != 0: if self.list_box.size() != 0:
MessageDialog("info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"]) MessageDialog("info", Msg.STR["sel_list"], title=Msg.STR["sel_tl"])

View File

@ -3,6 +3,8 @@
import logging import logging
import gettext import gettext
import locale import locale
import tkinter as tk
import os
from pathlib import Path from pathlib import Path
from subprocess import CompletedProcess, run from subprocess import CompletedProcess, run
from typing import Dict, Any from typing import Dict, Any
@ -57,18 +59,18 @@ class AppConfig:
# Updates # Updates
# 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year # 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
VERSION: str = "v. 2.06.2425" VERSION: str = "v. 2.07.0225"
UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/Wire-Py/releases" 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" DOWNLOAD_URL: str = "https://git.ilunix.de/punix/Wire-Py/archive"
# UI configuration # UI configuration
UI_CONFIG: Dict[str, Any] = { UI_CONFIG: Dict[str, Any] = {
"window_title": "Wire-Py", "window_title": "",
"window_title2": "LogViewer", "window_title2": "LogViewer",
"window_size": (600, 383), "window_size": (590, 450),
"font_family": "Ubuntu", "font_family": "Ubuntu",
"font_size": 11, "font_size": 11,
"resizable_window": (False, False), "resizable_window": (True, True),
} }
# System-dependent paths # System-dependent paths
@ -79,20 +81,6 @@ class AppConfig:
"pkey_path": "/usr/local/etc/ssl/pwgk.pem", "pkey_path": "/usr/local/etc/ssl/pwgk.pem",
} }
# Images and icons paths
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",
"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",
"icon_log": "/usr/share/icons/lx-icons/48/log.png",
}
@staticmethod @staticmethod
def setup_translations() -> gettext.gettext: def setup_translations() -> gettext.gettext:
""" """
@ -170,6 +158,65 @@ class AppConfig:
_ = AppConfig.setup_translations() _ = AppConfig.setup_translations()
class Image:
def __init__(self):
self.images = {}
def load_image(self, image_key, fallback_paths=None) -> None | tk.PhotoImage:
"""Load PNG image using tk.PhotoImage with fallback options"""
if image_key in self.images:
return self.images[image_key]
# Define image paths based on key
image_paths = {
"icon_header": [
"/usr/share/icons/lx-icons/32/wg_vpn.png",
],
"icon_vpn": [
"/usr/share/icons/lx-icons/48/wg_vpn.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_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",
],
"update": [
"/usr/share/icons/lx-icons/16/settings.png",
],
}
# Get paths to try
paths_to_try = image_paths.get(image_key, [])
# Add fallback paths if provided
if fallback_paths:
paths_to_try.extend(fallback_paths)
# Try to load image from paths
for path in paths_to_try:
try:
if os.path.exists(path):
photo = tk.PhotoImage(file=path)
self.images[image_key] = photo
return photo
except tk.TclError as e:
print(f"{LocaleStrings.MSGP['fail_load_image']}{path}: {e}")
continue
# Return None if no image found
return None
class Msg: class Msg:
""" """
A utility class that provides centralized access to translated message strings. A utility class that provides centralized access to translated message strings.
@ -193,6 +240,8 @@ class Msg:
STR: Dict[str, str] = { STR: Dict[str, str] = {
# Strings for messages # Strings for messages
"header_left_bottom": _("Simple GUI for Wireguard"),
"header_right_top": _("Wireguard VPN Manager"),
"sel_tl": _("Select tunnel"), "sel_tl": _("Select tunnel"),
"ren_err": _("Renaming not possible"), "ren_err": _("Renaming not possible"),
"exp_succ": _("Export successful"), "exp_succ": _("Export successful"),